﻿namespace System.ServiceModel.DomainServices.Client
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Threading;

    public abstract class EntityContainer : IRevertibleChangeTracking, IChangeTracking, INotifyPropertyChanged
    {
        private int _dirtySetCount;
        private IDictionary<Type, Type> _entityRootTypes = new Dictionary<Type, Type>();
        private IDictionary<Type, EntitySet> _entitySets = new Dictionary<Type, EntitySet>();
        private IDictionary<string, Type> _entityTypeMap = new Dictionary<string, Type>();
        private IDictionary<Type, EntitySet> _referencedEntitySets = new Dictionary<Type, EntitySet>();
        private System.ComponentModel.DataAnnotations.ValidationContext _validationContext;
        private PropertyChangedEventHandler propertyChanged;

        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                PropertyChangedEventHandler handler2;
                PropertyChangedEventHandler propertyChanged = this.propertyChanged;
                do
                {
                    handler2 = propertyChanged;
                    PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler) Delegate.Combine(handler2, value);
                    propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
                }
                while (propertyChanged != handler2);
            }
            remove
            {
                PropertyChangedEventHandler handler2;
                PropertyChangedEventHandler propertyChanged = this.propertyChanged;
                do
                {
                    handler2 = propertyChanged;
                    PropertyChangedEventHandler handler3 = (PropertyChangedEventHandler) Delegate.Remove(handler2, value);
                    propertyChanged = Interlocked.CompareExchange<PropertyChangedEventHandler>(ref this.propertyChanged, handler3, handler2);
                }
                while (propertyChanged != handler2);
            }
        }

        protected EntityContainer()
        {
        }

        protected void AcceptChanges()
        {
            foreach (KeyValuePair<Type, EntitySet> pair in this._entitySets)
            {
                pair.Value.AcceptChanges();
            }
        }

        internal void AddEntitySet(EntitySet set, EntitySetOperations supportedOperations)
        {
            set.Initialize(this, supportedOperations);
            this._entitySets[set.EntityType] = set;
            this._entityTypeMap[set.EntityType.Name] = set.EntityType;
        }

        public void AddReference(EntitySet entitySet)
        {
            if (entitySet == null)
            {
                throw new ArgumentNullException("entitySet");
            }
            Type entityType = entitySet.EntityType;
            if (this._entitySets.ContainsKey(entityType) || this._referencedEntitySets.ContainsKey(entityType))
            {
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resource.EntityContainer_EntitySetAlreadyExists, new object[] { entityType }));
            }
            this._referencedEntitySets.Add(entityType, entitySet);
        }

        internal void CheckCrossContainer(Entity entity)
        {
            EntityContainer container = (entity.LastSet != null) ? entity.LastSet.EntityContainer : null;
            if ((container != null) && (container != this))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.EntityContainer_CrossContainerAttach, new object[] { entity }));
            }
        }

        public void Clear()
        {
            foreach (KeyValuePair<Type, EntitySet> pair in this._entitySets)
            {
                pair.Value.Clear();
            }
        }

        internal void CompleteChildChanges(Entity entity, bool acceptChanges)
        {
            foreach (Type type in entity.MetaType.ChildTypes)
            {
                foreach (Entity entity2 in this.GetEntitySet(type).InterestingEntities.ToArray<Entity>())
                {
                    if ((entity2.Parent == entity) && (entity2.EntityState != EntityState.Unmodified))
                    {
                        if (acceptChanges)
                        {
                            ((IChangeTracking) entity2).AcceptChanges();
                        }
                        else
                        {
                            ((IRevertibleChangeTracking) entity2).RejectChanges();
                        }
                    }
                }
            }
        }

        protected void CreateEntitySet<TEntity>() where TEntity: Entity
        {
            this.CreateEntitySet<TEntity>(EntitySetOperations.None);
        }

        protected void CreateEntitySet<TEntity>(EntitySetOperations supportedOperations) where TEntity: Entity
        {
            EntitySet set = new EntitySet<TEntity>();
            this.AddEntitySet(set, supportedOperations);
        }

        public EntityChangeSet GetChanges()
        {
            List<Entity> list = new List<Entity>();
            List<Entity> list2 = new List<Entity>();
            List<Entity> list3 = new List<Entity>();
            foreach (KeyValuePair<Type, EntitySet> pair in this._entitySets)
            {
                foreach (Entity entity in pair.Value.InterestingEntities)
                {
                    if (entity.EntityState == EntityState.New)
                    {
                        list.Add(entity);
                    }
                    else
                    {
                        if (entity.EntityState == EntityState.Modified)
                        {
                            list2.Add(entity);
                            continue;
                        }
                        if (entity.EntityState == EntityState.Deleted)
                        {
                            list3.Add(entity);
                        }
                    }
                }
            }
            return new EntityChangeSet(list.AsReadOnly(), list2.AsReadOnly(), list3.AsReadOnly());
        }

        public EntitySet<TEntity> GetEntitySet<TEntity>() where TEntity: Entity
        {
            EntitySet<TEntity> entitySet = this.GetEntitySet(typeof(TEntity)) as EntitySet<TEntity>;
            if (entitySet == null)
            {
                Type rootEntityType = this.GetRootEntityType(typeof(TEntity));
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.EntityContainer_CannotRetrieveEntitySetForDerivedEntity, new object[] { typeof(TEntity).Name, rootEntityType.Name }));
            }
            return entitySet;
        }

        public EntitySet GetEntitySet(Type entityType)
        {
            EntitySet set;
            if (!this.TryGetEntitySet(entityType, out set))
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resource.EntityContainerDoesntContainEntityType, new object[] { entityType }));
            }
            return set;
        }

        private Type GetRootEntityType(Type entityType)
        {
            Type type = null;
            if (!this._entityRootTypes.TryGetValue(entityType, out type))
            {
                for (Type type2 = entityType; type2 != null; type2 = type2.BaseType)
                {
                    if (this._entitySets.ContainsKey(type2) || this._referencedEntitySets.ContainsKey(type2))
                    {
                        type = type2;
                        this._entityRootTypes[entityType] = type;
                        return type;
                    }
                }
            }
            return type;
        }

        internal bool HasChildChanges(Entity entity)
        {
            foreach (Type type in entity.MetaType.ChildTypes)
            {
                foreach (Entity entity2 in this.GetEntitySet(type).InterestingEntities)
                {
                    if ((entity2.Parent == entity) && (entity2.EntityState != EntityState.Unmodified))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        public IEnumerable LoadEntities(IEnumerable entities)
        {
            return this.LoadEntities(entities, LoadBehavior.KeepCurrent);
        }

        public IEnumerable LoadEntities(IEnumerable entities, LoadBehavior loadBehavior)
        {
            if (entities == null)
            {
                throw new ArgumentNullException("entities");
            }
            List<Entity> list = new List<Entity>();
            foreach (IGrouping<EntitySet, Entity> grouping in from p in entities.Cast<Entity>() group p by this.GetEntitySet(p.GetType()))
            {
                EntitySet key = grouping.Key;
                list.AddRange(key.LoadEntities(grouping, loadBehavior));
            }
            return list;
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (this.propertyChanged != null)
            {
                this.propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void RejectChanges()
        {
            foreach (KeyValuePair<Type, EntitySet> pair in this._entitySets)
            {
                pair.Value.RejectChanges();
            }
        }

        internal Type ResolveEntityType(string typeName)
        {
            Type type = null;
            this._entityTypeMap.TryGetValue(typeName, out type);
            return type;
        }

        internal void SetPropertyChanged(EntitySet entitySet, string propertyName)
        {
            if (string.CompareOrdinal(propertyName, "HasChanges") == 0)
            {
                if (entitySet.HasChanges)
                {
                    if (this._dirtySetCount++ == 0)
                    {
                        this.RaisePropertyChanged("HasChanges");
                    }
                }
                else if ((this._dirtySetCount > 0) && (--this._dirtySetCount == 0))
                {
                    this.RaisePropertyChanged("HasChanges");
                }
            }
        }

        void IChangeTracking.AcceptChanges()
        {
            this.AcceptChanges();
        }

        void IRevertibleChangeTracking.RejectChanges()
        {
            this.RejectChanges();
        }

        public bool TryGetEntitySet(Type entityType, out EntitySet entitySet)
        {
            Type rootEntityType = this.GetRootEntityType(entityType);
            if ((rootEntityType != null) && (this._entitySets.TryGetValue(rootEntityType, out entitySet) || this._referencedEntitySets.TryGetValue(rootEntityType, out entitySet)))
            {
                return true;
            }
            entitySet = null;
            return false;
        }

        public IEnumerable<EntitySet> EntitySets
        {
            get
            {
                foreach (EntitySet iteratorVariable0 in this._entitySets.Values)
                {
                    yield return iteratorVariable0;
                }
            }
        }

        public bool HasChanges
        {
            get
            {
                return Enumerable.Any<KeyValuePair<Type, EntitySet>>(this._entitySets, (Func<KeyValuePair<Type, EntitySet>, bool>) (p => p.Value.IsChanged));
            }
        }

        bool IChangeTracking.IsChanged
        {
            get
            {
                return this.HasChanges;
            }
        }

        internal System.ComponentModel.DataAnnotations.ValidationContext ValidationContext
        {
            get
            {
                return this._validationContext;
            }
            set
            {
                this._validationContext = value;
            }
        }

    }
}

